-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
--
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.
local log = require 'mc.logging'
local cjson = require 'cjson'
local ETH_GROUP_INTERFACE<const> = 'bmc.kepler.Managers.EthernetInterfaces.EthGroup'

local m = {}

local SERVER_CNT = 3

function m.get_vlan_enable(req_vlan)
    -- 如果只传了vlan_id表示要开启vlan
    if req_vlan ~= nil and req_vlan.VLANEnable == nil and req_vlan.VLANId ~= nil then
        return true
    end
    if req_vlan == nil or req_vlan.VLANEnable == nil then
        local ethernet = mdb.get_object(bus, '/bmc/kepler/Managers/1/EthernetInterfaces',
            'bmc.kepler.Managers.EthernetInterfaces')
        return ethernet.VLANEnable
    else
        return req_vlan.VLANEnable
    end
end

function m.get_vlan_id(req_vlan, origin_vlan)
    -- 如果只传了vlan_enable为fasle没传vlan_id表示关闭vlan，则vlan_id返回0
    if req_vlan ~= nil and req_vlan.VLANEnable == false and req_vlan.VLANId == nil then
        return 0
    end
    if req_vlan ~= nil and req_vlan.VLANEnable == true and req_vlan.VLANId == nil and origin_vlan.VLANId == nil then
        local err = base_messages.PropertyMissing('VLAN/VLANId')
        err.RelatedProperties = {'#/VLAN'}
        error(err)
    end
    if req_vlan ~= nil and req_vlan.VLANEnable == true and (origin_vlan.VLANId < 1 or origin_vlan.VLANId > 4094) then
        local err = custom_messages.PropertyValueOutOfRange(origin_vlan.VLANId, 'VLAN/VLANId')
        err.RelatedProperties = {'#/VLAN'}
        error(err)
    end
    if req_vlan ~= nil and req_vlan.VLANEnable == nil and (req_vlan.VLANId < 1 or req_vlan.VLANId > 4094) then
        local err = custom_messages.PropertyValueOutOfRange(req_vlan.VLANId, 'VLAN/VLANId')
        err.RelatedProperties = {'#/VLAN'}
        error(err)
    end
    if req_vlan == nil or req_vlan.VLANId == nil then
        local ethernet = mdb.get_object(bus, '/bmc/kepler/Managers/1/EthernetInterfaces',
            'bmc.kepler.Managers.EthernetInterfaces')
        return ethernet.VLANId
    else
        return req_vlan.VLANId
    end
end

function m.get_dedicated_vlan_enable(req_vlan)
    -- 如果只传了vlan_id表示要开启vlan
    if req_vlan ~= nil and req_vlan.VLANEnable == nil and req_vlan.VLANId ~= nil then
        return true
    end
    if req_vlan ~= nil and req_vlan.VLANEnable == false and req_vlan.VLANId ~= nil and req_vlan.VLANId ~= 0 then
        local err = custom_messages.VLANInfoConflict()
        err.RelatedProperties = {'#/VLAN/VLANEnable', '#/VLAN/VLANId'}
        error(err)
    end
    return req_vlan.VLANEnable
end

function m.get_dedicated_vlan_id(req_vlan, origin_vlan)
    -- 如果只传了vlan_enable为fasle没传vlan_id表示关闭vlan，则vlan_id返回0
    if req_vlan ~= nil and req_vlan.VLANEnable == false and req_vlan.VLANId == nil then
        return 0
    end
    if req_vlan ~= nil and req_vlan.VLANEnable == true and req_vlan.VLANId == nil and origin_vlan.VLANId == nil then
        local err = base_messages.PropertyMissing('VLAN/VLANId')
        err.RelatedProperties = {'#/VLAN'}
        error(err)
    end
    if req_vlan ~= nil and req_vlan.VLANEnable == true and (origin_vlan.VLANId < 1 or origin_vlan.VLANId > 4094) then
        local err = custom_messages.PropertyValueOutOfRange(origin_vlan.VLANId, 'VLAN/VLANId')
        err.RelatedProperties = {'#/VLAN'}
        error(err)
    end
    if req_vlan ~= nil and req_vlan.VLANEnable == nil and (req_vlan.VLANId < 1 or req_vlan.VLANId > 4094) then
        local err = custom_messages.PropertyValueOutOfRange(req_vlan.VLANId, 'VLAN/VLANId')
        err.RelatedProperties = {'#/VLAN'}
        error(err)
    end
    return req_vlan.VLANId
end

function m.set_address()
    local err = base_messages.PropertyNotWritable('IPv4Addresses')
    err.RelatedProperties = {'#/IPv4Addresses'}
    error(err)
end

function m.check_management_network_port(object)
    if object.Type == nil and object.PortNumber ~= nil then
        local err = custom_messages.PropertyValueConflict(
            'Oem/{{OemIdentifier}}/ManagementNetworkPort/PortNumber',
            'Oem/{{OemIdentifier}}/ManagementNetworkPort/Type')
        err.RelatedProperties = {
            '#/Oem/{{OemIdentifier}}/ManagementNetworkPort/PortNumber',
            '#/Oem/{{OemIdentifier}}/ManagementNetworkPort/Type'}
        error(err)
    end

    if object.PortNumber == nil and object.Type ~= nil then
        local err = custom_messages.PropertyValueConflict(
            'Oem/{{OemIdentifier}}/ManagementNetworkPort/Type',
            'Oem/{{OemIdentifier}}/ManagementNetworkPort/PortNumber')
        err.RelatedProperties = {
            '#/Oem/{{OemIdentifier}}/ManagementNetworkPort/Type',
            '#/Oem/{{OemIdentifier}}/ManagementNetworkPort/PortNumber'}
        error(err)
    end

    return true
end

function m.check_port(path, type, port_number)
    log:notice('path = %s, type = %s, port_number = %s', path, type, port_number)
    if path == '' then
        local err = custom_messages.PortNotExist(type, tostring(port_number))
        err.RelatedProperties = {
            '#/Oem/{{OemIdentifier}}/ManagementNetworkPort/Type',
            '#/Oem/{{OemIdentifier}}/ManagementNetworkPort/PortNumber'}
        error(err)
    end
    return true
end

function m.check_vlan_setting(vlan, origin_vlan)
    if vlan.VLANEnable == true then
        if vlan.VLANId == nil and origin_vlan.VLANId == nil then
            local err = base_messages.PropertyMissing('VLAN/VLANId')
            err.RelatedProperties = {'#/VLAN'}
            error(err)
        end

        if vlan.VLANId == nil and origin_vlan.VLANId ~= nil then
            local err = custom_messages.PropertyValueConflict('VLAN/VLANEnable', 'VLAN/VLANId')
            err.RelatedProperties = {'#/VLAN/VLANEnable', '#/VLAN/VLANId'}
            error(err)
        end
    end

    if vlan.VLANEnable == false and vlan.VLANId ~= nil then
        local err = custom_messages.VLANInfoConflict()
        err.RelatedProperties = {'#/VLAN'}
        error(err)
    end

    return true
end

function m.get_switch_connections(conns)
    local objs = cjson.decode(conns)
    local switch_conns = cjson.json_object_new_array()
    for _, v in pairs(objs) do
        local conn = cjson.json_object_new_object()
        conn.SwitchManagementIP = v.SwitchManagementIP
        conn.SwitchConnectionPortIDs = cjson.json_object_new_array()
        for _, vv in pairs(v.SwitchConnectionPortIDs) do
            conn.SwitchConnectionPortIDs[#conn.SwitchConnectionPortIDs + 1] = vv
        end
        switch_conns[#switch_conns + 1] = conn
    end
    return switch_conns
end

function m.check_ipv6_enabled(enable, ipversion)
    -- 仅在IPv4下才需要进行使能,否则不需要更新
    if enable then
        return ipversion == 'IPv4'
    end

    -- 仅开启IPv6模式下不允许关闭
    if ipversion == 'IPv6' then
        local err = custom_messages.PropertyModificationNotSupported()
        err.RelatedProperties = {'#/IPv6Enabled'}
        error(err)
    end

    return ipversion == 'IPv4AndIPv6'
end

function m.check_user_ntp_servers(enable, ipversion, ipv4mode, current_ntp_mode)
    if not enable then
        return current_ntp_mode == 'IPv4'
    end

    -- 开启使能时需要IPv4开启DHCP
    if ipversion == 'IPv6' or ipv4mode == 'Static' then
        local err = custom_messages.PropertyModificationNotSupported('UseNTPServers')
        err.RelatedProperties = {'#/DHCPv4/UseNTPServers'}
        error(err)
    end

    return true
end

-- 检查NameServers和StaticNameServers所含参数是否相同
function m.check_name_servers(name_servers_info, static_name_servers_info)
    if name_servers_info == nil or static_name_servers_info == nil then
        return true
    end
    local ns1, ns2
    for i = 1, SERVER_CNT do
        ns1 = name_servers_info[i]
        ns2 = static_name_servers_info[i]
        if ns1 ~= ns2 then
            return false
        end
    end

    return true
end

function m.check_dns_mode(is_name_servers, id)
    local dns_obj = mdb.get_object(bus, '/bmc/kepler/Managers/' .. id .. '/EthernetInterfaces/DNS',
            'bmc.kepler.Managers.EthernetInterfaces.DNS')
    if not dns_obj then
        log:error('Get DNS object failed')
        error(base_messages.InternalError())
    end
    if dns_obj.DHCPv4UseDNSServers == true or dns_obj.DHCPv6UseDNSServers == true then
        local err
        if is_name_servers then
            err = base_messages.PropertyNotWritable('NameServers')
            err.RelatedProperties = {'#/NameServers'}
        else
            err = base_messages.PropertyNotWritable('StaticNameServers')
            err.RelatedProperties = {'#/StaticNameServers'}
        end
        error(err)
    end
    return true
end

function m.check_dns_addr_and_mode(name_servers_info, static_name_servers_info, is_name_servers, id)
    -- 请求体未传入NameServers和StaticNameServers同时为空，不执行后续操作
    if name_servers_info == nil and static_name_servers_info == nil then
        return false
    end
    if m.check_name_servers(name_servers_info, static_name_servers_info) == false then
        local err
        if is_name_servers then
            err = custom_messages.PropertyValueConflict('NameServers', 'StaticNameServers')
        else
            err = custom_messages.PropertyValueConflict('StaticNameServers', 'NameServers')
        end
        err.RelatedProperties = {'#/NameServers', '#/StaticNameServers'}
        error(err)
    end
    if is_name_servers and name_servers_info == nil then
        is_name_servers = false
    elseif (not is_name_servers) and static_name_servers_info == nil then
        is_name_servers = true
    end
    return m.check_dns_mode(is_name_servers, id)
end

function m.get_netmode()
    local ethernet = mdb.get_object(bus, '/bmc/kepler/Managers/1/EthernetInterfaces',
            'bmc.kepler.Managers.EthernetInterfaces')
    return ethernet.NetMode
end

function m.get_portid()
    local ethernet = mdb.get_object(bus, '/bmc/kepler/Managers/1/EthernetInterfaces',
            'bmc.kepler.Managers.EthernetInterfaces')
    return ethernet.PortId
end

function m.get_chassis_lan_subnet(group_paths)
    local subnet
    for _, group_path in ipairs(group_paths) do
        local ok, eth_group = pcall(mdb.get_object, bus, group_path, ETH_GROUP_INTERFACE)
        if ok and eth_group.OutType == 1 and eth_group.InnerNetWork then
            subnet = eth_group.InnerNetWork
            break
        end
    end

    if subnet == nil then
        return ''
    end

    local subnet_parts = {}
    for part in subnet:gmatch('%d+') do
        table.insert(subnet_parts, part)
    end

    local chassis_lan_subnet = string.format('%s.%s.0.0/16', subnet_parts[1], subnet_parts[2])
    return chassis_lan_subnet
end

return m
